package br.gov.cemaden.sendmail;

import br.gov.cemaden.sendmail.exception.SendmailConfigurationException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Date;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 *
 * @author alan
 */
public class Sendmail {
    
    private final String subject;
    private final String body;
    private String host, email, password, from;
    private final Set<String> recipients = new CopyOnWriteArraySet<String>();
    private Integer port;    
    private Protocol protocol = Protocol.SMTP;
    private boolean auth = true;
    private final boolean debug = true;
    
    /**
     * Creates an environment for sending e-mails with basic data
     * @param subject subject of the message
     * @param body body (content) of the message
     */
    public Sendmail(String subject, String body){
        this.subject = subject;
        this.body = body;
    }
    
    /**
     * Creates an environment for sending e-mails with basic data and properties file data to fill up send e-mails requirements
     * @param subject subject of the message
     * @param body body (content) of the message
     * @param path a properties file containing the key/value pairs to fill the class attributes. This attributes can be set again in it's respective methods.
     */
    public Sendmail(String subject, String body, Path path){
        this.subject = subject;
        this.body = body;
        Properties props = new Properties();
        try {
            props.load(new FileInputStream(path.toFile()));
        } catch (FileNotFoundException ex) {
            System.err.println("The specified file " + path.toString() + " was not found - " + ex);
        } catch (IOException ex) {
            System.err.println("Error when trying to read the specified file " + path.toString() + " - " + ex);
        }
        loadProperties(props);
    }    
    
    /**
     * 
     * @param host the host by which the email is sent
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setHost(String host){
        this.host = host;
        return this;
    }
    
    /**
     * 
     * @return the host being set
     */
    public String getHost(){
        return this.host;
    }
    
    /**
     * 
     * @param port
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setPort(int port){
        this.port = port;
        return this;
    }
    
    /**
     * 
     * @return the host port being used
     */
    public int getPort(){
        return this.port;
    }
    
    /**
     * 
     * @param email
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setEmail(String email){
        this.email = email;
        return this;
    }
    
    /**
     * 
     * @return the email being used to send the email
     */
    public String getEmail(){
        return this.email;
    }
    
    /**
     * 
     * @param password
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setPassword(String password){
        this.password = password;
        return this;
    }
    
    /**
     * 
     * @param from
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setFrom(String from){
        this.from = from;
        return this;
    }
    
    /**
     * 
     * @return from attribute
     */
    public String getFrom(){
        return this.from;
    }
    
    /**
     * 
     * @param recipient
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setRecipients(String recipient){
        if(!recipient.isEmpty())
            this.recipients.add(recipient);
        return this;
    }
    
    /**
     * 
     * @param recipients
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setRecipients(Set<String> recipients){
        if(!recipients.isEmpty())
            this.recipients.addAll(recipients);
        return this;
    }        
    
    /**
     * 
     * @return a set of e-mails ({@code String}) who will receive the email
     */
    public Set<String> getRecipients(){
        return this.recipients;
    }
    
    /**
     * 
     * @param protocol
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setProtocol(Protocol protocol){
        this.protocol = protocol;
        return this;
    }
    
    /**
     * 
     * @return the {@link Protocol} being used
     */
    public Protocol getProtocol(){
        return this.protocol;
    }
    
    /**
     * 
     * @param auth
     * @return the actual {@link Sendmail} object
     */
    public Sendmail setAuth(Boolean auth){
        this.auth = auth;
        return this;
    }
    
    /**
     * 
     * @return <tt>true</tt> if authentication is needed
     */
    public Boolean getAuth(){
        return this.auth;
    }
    
    /**
     * Method to send e-mails
     */
    public void send(){
        
        /* First of all, validate attributes */
        validateAttributes();                
                       
        /*
        * Transform e-mails set in an InternetAddress array.
        * First we create an InternetAddress array with our Set<String> recipients size
        * second we transform our Set<String> recipients in an array of Strings (temp)
        * then we iterate over it, putting in each index of InternetAddress array a new InternetAddress object,
        * passing to it the value of the equivalent index of temp array.
        */            
        InternetAddress[] addresses =  new InternetAddress[recipients.size()];            
        String[] temp = recipients.toArray(new String[recipients.size()]);            
        for(int i = 0; i < temp.length; i++){
            try {
                addresses[i] = new InternetAddress(temp[i]);
            } catch (AddressException ex) {
                System.err.println("Error when trying to create InternetAddress object!");
                System.err.println("The String " + temp[i] + " could not be parsed to InternetAddress object due to " + ex);
            }
        }
        
        /* Creates connection properties */                
        Properties props = new Properties();
        props.put("mail.smtp.host", host);
        props.put("mail.smtp.port", port);
        /* Depending on the Protocol type, an additional propertie is required */
        switch (protocol) {
            case SMTPS:
                props.put("mail.smtp.ssl.enable", true);
                break;
            case TLS:
                props.put("mail.smtp.starttls.enable", true);
                break;
        }
        
        /* Check for the necessity of authentication and, if true, do it. */
        
        Authenticator authenticator = null;
        if (auth) {
            props.put("mail.smtp.auth", true);
            authenticator = new Authenticator() {
                private PasswordAuthentication pa = new PasswordAuthentication(email, password);
                @Override
                public PasswordAuthentication getPasswordAuthentication() {
                    return pa;
                }
            };
        }
        
        /* Creates session basead on properties and authentication */
        
        Session session = Session.getInstance(props, authenticator);
        session.setDebug(debug);
        
        /* Creates MimeMessage object, the one which will be sent over Transport.send(Message message) method  */
        
        MimeMessage message = new MimeMessage(session);
        
        try {                                    
            /* Set MimeMessage attributes */            
            message.setRecipients(Message.RecipientType.TO, addresses);
            message.setFrom(new InternetAddress(this.from));
            message.setSubject(subject);
            message.setSentDate(new Date());
            message.setText(body);
            
            /* Send message */
            Transport.send(message);
        }         
        catch (MessagingException ex) {
            System.err.println("Exception when trying to send email: " + ex);
        }
    }
    
    /**
     * 
     */
    private void validateAttributes(){
        
        if(host.isEmpty()){
            throw new SendmailConfigurationException("The attribute host is empty.");
        }
        else if(email.isEmpty()){
            throw new SendmailConfigurationException("The attribute email is empty.");
        }
        else if(password.isEmpty()){
            throw new SendmailConfigurationException("The attribute password is empty.");
        }        
        
        if(!validateEmail(email)){
            throw new SendmailConfigurationException("The email " + email + " is not valid.");
        }
        
        /* If from attribute is empty, get the email account value to it. */
        if(from.isEmpty()){
            from = email;
        } /* If not, validate the from email attribute */
        else{ 
            if(!validateEmail(from)){
                throw new SendmailConfigurationException("The from attribute " + from + " is not valid.");
            }
        }
        
        /* Keep just valid emails in the set */
        for (String recipient : recipients) {
            if(!validateEmail(recipient)){
                recipients.remove(recipient);
            }   
        }
        /* If after validating the e-mails the set is empty, throw an exception */
        if(recipients.isEmpty()){
            throw new SendmailConfigurationException("The email's receivers were not set.");
        }
    }
    
    /**
     * 
     * @param email
     * @return <tt>true</tt> if the email is valid
     */
    private boolean validateEmail(String email) {
        try {
            new InternetAddress(email).validate(); 
        } catch (AddressException ex) {
            System.err.println("Address exception when trying to send an email - wrong email address: " + ex.getMessage());
            return false;
        }
        return true;
    }
    
    /**
     * 
     * @param props 
     */
    private void loadProperties(Properties props){
        this.host = props.getProperty("sendmail.smtp.host").trim();
        this.port = Integer.parseInt(props.getProperty("sendmail.smtp.port").trim());
        this.email = props.getProperty("sendmail.email").trim();
        this.password = props.getProperty("sendmail.password").trim();
        this.from = props.getProperty("sendmail.from").trim();
        String recipient = props.getProperty("sendmail.default.recipient").trim();
        if(!recipient.isEmpty()){
            this.recipients.add(recipient);            
        }
    }        
    
}